﻿namespace Common.Protocol
{
    using System;
    using System.Collections.Generic;

    public class ProtocolInterval
    {
        public List<Block> Blocks;

        public int StepMilliseconds { get; private set; }

        public DateTime CurrentTime { get; set; }

        public Block CurrentBlock { get; set; }

        public long StartEmptyMilliseconds { get; private set; }
        public long EndEmptyMilliseconds { get; private set; }

        public long TotalMilliseconds { get; private set; }

        public ProtocolInterval(DateTime currentTime, long totalMilliseconds, int stepMilliseconds)
        {
            this.StepMilliseconds = stepMilliseconds;

            this.StartEmptyMilliseconds = 0;
            this.EndEmptyMilliseconds = 0;

            this.Blocks = new List<Block>();

            if (currentTime < Protocol.Blocks[0].Start)
            {
                this.CurrentTime = Protocol.Blocks[0].Start;
            }
            else
            {
                if (currentTime > Protocol.Blocks[Protocol.Blocks.Count - 1].End)
                {
                    this.CurrentTime = Protocol.Blocks[Protocol.Blocks.Count - 1].End;
                }
                else
                {
                    this.CurrentTime = currentTime;
                }
            }


            int protocolBlockIndex = Protocol.FindBlockByTime(this.CurrentTime).Value;
            this.CurrentBlock = Protocol.Blocks[protocolBlockIndex];

            if (currentTime > this.CurrentBlock.End)
            {
                this.CurrentTime = this.CurrentBlock.End;
            }

            if (currentTime < this.CurrentBlock.Start)
            {
                this.CurrentTime = this.CurrentBlock.Start;
            }

            if (protocolBlockIndex != -1)
            {
                this.CopyBlocks(totalMilliseconds, protocolBlockIndex);
            }
            else
            {
                throw new ArgumentOutOfRangeException(@"Wrong time");
            }
        }

        private void CopyBlocks(long totalMilliseconds, int currentBlockIndex)
        {
            long halfMilliseconds = totalMilliseconds / 2;

            Block currentBlock = Protocol.Blocks[currentBlockIndex].Copy(this.CurrentTime, totalMilliseconds);
            this.Blocks.Add(currentBlock);
            long millisecondsBeforeCurrentBlock, millisecondsAfterCurrentBlock;
            millisecondsBeforeCurrentBlock = (long)(halfMilliseconds
                                             - (this.CurrentTime - currentBlock.Start).TotalMilliseconds);
            millisecondsAfterCurrentBlock = (long)(halfMilliseconds - (currentBlock.End - this.CurrentTime).TotalMilliseconds);

            this.TotalMilliseconds = currentBlock.TotalMilliseconds;
            this.CopyBeforeCurrentBlock(millisecondsBeforeCurrentBlock, currentBlockIndex);
            this.CopyAfterCurrentBlock(millisecondsAfterCurrentBlock, currentBlockIndex);

            this.Blocks.RemoveAll(b => b.TotalMilliseconds == 0);

            this.TotalMilliseconds += this.StartEmptyMilliseconds;
            this.TotalMilliseconds += this.EndEmptyMilliseconds;
        }

        private void CopyBeforeCurrentBlock(long milliseconds, int currentBlockIndex)
        {
            Block block, toInsert;

            long millisecondsLeft = milliseconds;
            for (int i = currentBlockIndex - 1; i >= 0; i--)
            {
                block = Protocol.Blocks[i];
                try
                {
                    toInsert = block.CopyFromEnd(millisecondsLeft);
                }
                catch (ArgumentOutOfRangeException)
                {
                    return;
                }

                this.Blocks.Insert(0, toInsert);
                millisecondsLeft -= toInsert.TotalMilliseconds;
                this.TotalMilliseconds += toInsert.TotalMilliseconds;
                if (millisecondsLeft <= 100)
                {
                    break;
                }
            }

            if (millisecondsLeft >= 100)
            {
                this.StartEmptyMilliseconds = millisecondsLeft;
            }
        }

        private void CopyAfterCurrentBlock(long milliseconds, int currentBlockIndex)
        {
            Block block, toAdd;
            long millisecondsLeft = milliseconds;
            for (int i = currentBlockIndex + 1; i < Protocol.Blocks.Count; i++)
            {
                block = Protocol.Blocks[i];
                toAdd = block.CopyFromStart(millisecondsLeft);
                this.Blocks.Add(toAdd);
                millisecondsLeft -= toAdd.TotalMilliseconds;
                this.TotalMilliseconds += toAdd.TotalMilliseconds;
                if (millisecondsLeft < 100)
                {
                    break;
                }
            }

            if (millisecondsLeft >= 100)
            {
                this.EndEmptyMilliseconds = millisecondsLeft;
            }
        }
    }
}